package com.lagou.niancheng.rpc.client;


import com.lagou.niancheng.rpc.client.netty.NettyConnectionConfig;
import com.lagou.niancheng.rpc.client.netty.NettyConnectionSource;
import com.lagou.niancheng.rpc.common.converter.Converter;
import com.lagou.niancheng.rpc.common.converter.FastJsonConverter;
import com.lagou.niancheng.rpc.common.entity.RPCMethodMetaData;
import com.lagou.niancheng.rpc.common.entity.RPCRequestData;
import com.lagou.niancheng.rpc.common.entity.RPCResponseData;
import com.lagou.niancheng.rpc.common.entity.RPCServiceMetaData;
import com.lagou.niancheng.rpc.common.entity.ServerGetRequest;
import com.lagou.niancheng.rpc.common.exceptions.SimpleRpcException;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

/**
 * rpc接口代理工厂
 *
 * @author 大犬
 * @date 2020/4/27
 */
public class SimpleRpcMethodProxyFactory {

    private ConnectionSource connectionSource;
    private Converter converter;

    public SimpleRpcMethodProxyFactory() {
        this(new NettyConnectionSource(new NettyConnectionConfig()), new FastJsonConverter());
    }

    public SimpleRpcMethodProxyFactory(ConnectionSource connectionSource, Converter converter) {
        this.connectionSource = connectionSource;
        this.converter = converter;

    }

    @SuppressWarnings("unchecked")
    public <T> T createRpcProxy(Class<T> serviceInterface) {
        String serviceName = serviceInterface.getName();
        // 获取服务元数据
        RPCServiceMetaData rpcServiceMetaData = getServiceMetaData(serviceName);
        // 创建rpc接口代理对象
        return (T) Proxy.newProxyInstance(SimpleRpcMethodProxyFactory.class.getClassLoader(),
                new Class[]{serviceInterface},
                new RpcProxy(connectionSource, rpcServiceMetaData, serviceInterface, converter));
    }

    private RPCServiceMetaData getServiceMetaData(String serviceName) {
        Connection connection = connectionSource.getConnection();
        ServerGetRequest serverGetRequest = new ServerGetRequest();
        serverGetRequest.setServerTypeName(serviceName);
        return (RPCServiceMetaData) connection.sendRequestData(serverGetRequest);
    }


    private static class RpcProxy implements InvocationHandler {
        private Map<Method, String> methodIdMap;
        private ConnectionSource connectionSource;
        private RPCServiceMetaData rpcServiceMetaData;
        private Class serviceInterface;
        private Converter converter;

        public RpcProxy(ConnectionSource connectionSource,
                        RPCServiceMetaData rpcServiceMetaData,
                        Class serviceInterface, Converter converter) {
            this.connectionSource = connectionSource;
            this.rpcServiceMetaData = rpcServiceMetaData;
            this.serviceInterface = serviceInterface;
            methodIdMap = new HashMap<>();
            this.converter = converter;
            initMethodMap();
        }

        private void initMethodMap() {
            Map<String, RPCMethodMetaData> methodMetaDataMap = rpcServiceMetaData.getMethodMetaDataMap();
            if (methodMetaDataMap == null || methodMetaDataMap.isEmpty()) {
                return;
            }
            methodMetaDataMap.forEach((id, data) -> {
                String[] paramTypeNames = data.getParamTypeNames();
                Class[] paramTypes = getParamTypes(paramTypeNames);
                try {
                    @SuppressWarnings("unchecked")
                    Method method = serviceInterface.getDeclaredMethod(data.getMethodName(), paramTypes);
                    methodIdMap.put(method, id);
                } catch (NoSuchMethodException e) {
                    throw new SimpleRpcException("find method failed", e);
                }
            });
        }

        private Class[] getParamTypes(String[] paramTypeNames) {
            if (paramTypeNames == null || paramTypeNames.length == 0) {
                return null;
            }
            Class[] paramTypes = new Class[paramTypeNames.length];
            for (int i = 0; i < paramTypeNames.length; i++) {
                String paramTypeName = paramTypeNames[i];
                try {
                    paramTypes[i] = Class.forName(paramTypeName);
                } catch (ClassNotFoundException e) {
                    throw new SimpleRpcException("load paramType failed", e);
                }
            }
            return paramTypes;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodId = methodIdMap.get(method);
            if (methodId == null) {
                return null;
            }
            // 获取连接
            Connection connection = connectionSource.getConnection();
            // 构建请求数据
            RPCRequestData rpcRequestData = buildRequestData(methodId, args);
            // 发送请求数据
            RPCResponseData rpcResponseData = (RPCResponseData) connection.sendRequestData(rpcRequestData);
            // 结果处理
            if (rpcResponseData.getOccurExp()) {
                // 如果有异常，则直接抛出
                throw rpcResponseData.getException();
            } else {
                // 返回结果
                return converter.bytes2Obj(rpcResponseData.getReturnObject(), method.getGenericReturnType());
            }
        }

        private RPCRequestData buildRequestData(String methodId, Object[] args) {

            byte[][] paramBytes;
            if (args == null || args.length == 0) {
                paramBytes = null;
            } else {
                paramBytes = new byte[args.length][];
                for (int i = 0; i < args.length; i++) {
                    paramBytes[i] = converter.obj2Bytes(args[i]);
                }
            }
            return new RPCRequestData()
                    .setMethodId(methodId)
                    .setParams(paramBytes);
        }


    }


}
